package ee.ut.math.chess.game.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import ee.ut.math.chess.base.model.Board;
import ee.ut.math.chess.base.model.Move;
import ee.ut.math.chess.base.model.SpecialMoveType;
import ee.ut.math.chess.base.model.chesspiece.ChessPiece;
import ee.ut.math.chess.base.model.chesspiece.Color;
import ee.ut.math.chess.base.model.exception.GameLogicException;
import ee.ut.math.chess.base.model.exception.KingIsThreatenedException;
import ee.ut.math.chess.base.model.exception.MoveNotAllowedException;
import ee.ut.math.chess.hadoop.HadoopManager;

public class Game implements Serializable {
	private static final long serialVersionUID = 1L;

	private UUID gameId;
	private Board board;
	private Color waitingForPlayer;
	private int depth = 0;
	private long lastMoveTimestamp;
	private List<Move> moves = new ArrayList<Move>();
	private GameStatus gameStatus = GameStatus.ONGOING;
	private Color computer;
	private Move checkMove;

	public Game() {
		gameId = UUID.randomUUID();
		board = new Board();
		board.resetBoard();
		waitingForPlayer = Color.WHITE;
		computer = Color.BLACK;
		lastMoveTimestamp = System.currentTimeMillis();
		moves.clear();
	}

	public String toJSON() {
		StringBuilder buffer = new StringBuilder();
		buffer.append("{");
		buffer.append("\"id\":\"" + this.getId() + "\"\n");
		buffer.append(", \"board\":" + this.board.toJSON(checkMove) + "\n");
		buffer.append(", \"waitingFor\":\"" + this.waitingForPlayer + "\"\n");
		buffer.append(", \"lastMoveTimestamp\":" + this.lastMoveTimestamp
				+ "\n");
		buffer.append(", \"gameStatus\":\"" + this.gameStatus + "\"\n");
		buffer.append(", \"AI level\":\"" + this.depth + "\"\n");
		buffer.append("}");
		return buffer.toString();
	}

	public void move(Move move) throws GameLogicException {
		ChessPiece chessPiece = board.getChessPiece(move.getX1(), move.getY1());
		List<Move> legalMoves = chessPiece.getLegalMoves(move.getX1(),
				move.getY1(), this, false);

		int index = legalMoves.indexOf(move);
		if (index == -1) {
			throw new MoveNotAllowedException();
		}
		Move parsedMove = legalMoves.get(index);
		moveInternal(chessPiece, parsedMove);
		finishMove(chessPiece, parsedMove);
		if (moves.size() >= 3 && SpecialMoveType.CASTLING != parsedMove.getSpecialMoveType()) {
			endGameCheck();
		}
		if (waitingForPlayer == computer && !this.hasEnded()) {
			HadoopManager.doComputerMove(this);
		}
	}

	public void moveInternal(ChessPiece chessPiece, Move parsedMove)
			throws GameLogicException {
		DummyPersistenceManager manager = new DummyPersistenceManager(this);

		board.move(parsedMove);
		if (parsedMove.getSpecialMoveType() != null) {
			doAdditional(parsedMove, chessPiece);
		}
		Move threateningMove = GameLogic.kingIsThreatened(waitingForPlayer,
				this);
		if (threateningMove != null) { // move not allowed
			manager.rollback();
			throw new KingIsThreatenedException(threateningMove);
		}
	}

	public void finishMove(ChessPiece chessPiece, Move parsedMove) {
		moves.add(parsedMove);
		chessPiece.setInInitialPosition(false);
		lastMoveTimestamp = System.currentTimeMillis();
		nextTurn();
	}

	private void doAdditional(Move move, ChessPiece chessPiece) {
		if (SpecialMoveType.EN_PASSANT == move.getSpecialMoveType()) {
			if (chessPiece.getColor() == Color.WHITE) {
				board.removeChessPiece(move.getX2() - 1, move.getY2());
			} else {
				board.removeChessPiece(move.getX2() + 1, move.getY2());
			}
			return;
		}
		if (SpecialMoveType.CASTLING == move.getSpecialMoveType()) {
			if (move.getY2() == 6) {
				board.moveChessPiece(move.getX1(), 7, move.getX1(), 5);
			} else {
				board.moveChessPiece(move.getX1(), 0, move.getX1(), 3);
			}
		}
		if (SpecialMoveType.PROMOTING == move.getSpecialMoveType()) {
			board.promote(move.getX2(), move.getY2());
		}
	}
	
	public boolean hasEnded() {
		return this.gameStatus == GameStatus.CHECKMATE
				|| this.gameStatus == GameStatus.STALEMATE
				|| this.gameStatus == GameStatus.TIE;
	}

	private void endGameCheck() {
		GameLogic.setGameStatus(this);
	}
	
	public void nextTurn() {
		if (Color.BLACK == waitingForPlayer) {
			waitingForPlayer = Color.WHITE;
		} else {
			waitingForPlayer = Color.BLACK;
		}
	}
	
	
	/*
	 * ////////////////////////////////////////////////
	 * /////////////getters and setters////////////////
	 * //////////////////////////////////////////////// 
	 */

	public void setBoard(Board board) {
		this.board = board;
	}

	public List<Move> getMoves() {
		return moves;
	}

	public Color getWaitingForPlayer() {
		return waitingForPlayer;
	}


	public void setGameStatus(GameStatus gameStatus) {
		this.gameStatus = gameStatus;
	}

	public void setCheckMove(Move checkMove) {
		this.checkMove = checkMove;
	}

	public UUID getId() {
		return gameId;
	}

	public Board getBoard() {
		return board;
	}
	
	public int getDepth() {
		return depth;
	}

	public void setDepth(int depth) {
		this.depth = depth;
	}

	public long getLastMoveTimestamp() {
		return lastMoveTimestamp;
	}
}
